/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Xceed.Silverlight.Utils;
using System.ComponentModel;
using Xceed.Silverlight.ListBox.PositioningFunction;

namespace Xceed.Silverlight.ListBox
{
  public class PathConfiguration : DependencyObject, ISupportInitialize
  {
    #region ContainerOrientation Property

    public static readonly DependencyProperty ContainerOrientationProperty = DependencyProperty.Register(
      "ContainerOrientation",
      typeof( ContainerOrientation ),
      typeof( PathConfiguration ),
      new PropertyMetadata( ContainerOrientation.None ) );

    public ContainerOrientation ContainerOrientation
    {
      get
      {
        return ( ContainerOrientation )this.GetValue( PathConfiguration.ContainerOrientationProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.ContainerOrientationProperty, value );
      }
    }

    #endregion

    #region ContainerPadding Property

    public static readonly DependencyProperty ContainerPaddingProperty = DependencyProperty.Register(
      "ContainerPadding",
      typeof( double ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        10.0d,
        new PropertyChangedCallback( PathConfiguration.OnContainerPaddingChanged ) ) );

    public double ContainerPadding
    {
      get
      {
        return ( double )this.GetValue( PathConfiguration.ContainerPaddingProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.ContainerPaddingProperty, value );
      }
    }

    private static void OnContainerPaddingChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathGeometry Public Property

    public static readonly DependencyProperty PathGeometryProperty = DependencyProperty.Register(
      "PathGeometry",
      typeof( Geometry ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( PathConfiguration.OnPathGeometryChanged ) ) );

    public Geometry PathGeometry
    {
      get
      {
        return ( Geometry )this.GetValue( PathConfiguration.PathGeometryProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathGeometryProperty, value );
      }
    }

    private static void OnPathGeometryChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      if( e.NewValue is GeometryGroup )
      {
        throw new NotSupportedException( "GeometryGroup is not supported." );
      }

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathPadding Property

    public static readonly DependencyProperty PathPaddingProperty = DependencyProperty.Register(
      "PathPadding",
      typeof( Thickness ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        new Thickness(),
        new PropertyChangedCallback( PathConfiguration.OnPathPaddingChanged ) ) );

    public Thickness PathPadding
    {
      get
      {
        return ( Thickness )this.GetValue( PathConfiguration.PathPaddingProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathPaddingProperty, value );
      }
    }

    private static void OnPathPaddingChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathStretch Property

    public static readonly DependencyProperty PathStretchProperty = DependencyProperty.Register(
      "PathStretch",
      typeof( Stretch ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        Stretch.Fill,
        new PropertyChangedCallback( PathConfiguration.OnPathStretchChanged ) ) );

    public Stretch PathStretch
    {
      get
      {
        return ( Stretch )this.GetValue( PathConfiguration.PathStretchProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathStretchProperty, value );
      }
    }

    private static void OnPathStretchChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region StartOffset Property

    public static readonly DependencyProperty StartOffsetProperty = DependencyProperty.Register(
      "StartOffset",
      typeof( double ),
      typeof( PathConfiguration ),
      new PropertyMetadata( ( double )0.0d ) );

    public double StartOffset
    {
      get
      {
        return ( double )this.GetValue( PathConfiguration.StartOffsetProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.StartOffsetProperty, value );
      }
    }

    #endregion

    #region ScalingStops Public Property

    public ObservableCollection<ScalingStop> ScalingStops
    {
      get
      {
        if( m_scalingStops == null )
          m_scalingStops = new ObservableCollection<ScalingStop>();

        return m_scalingStops;
      }
    }

    private ObservableCollection<ScalingStop> m_scalingStops;

    #endregion

    #region ZIndexStops Public Property

    public ObservableCollection<ZIndexStop> ZIndexStops
    {
      get
      {
        if( m_zIndexStops == null )
          m_zIndexStops = new ObservableCollection<ZIndexStop>();

        return m_zIndexStops;
      }
    }

    private ObservableCollection<ZIndexStop> m_zIndexStops;

    #endregion


    #region FlattenedGeometry Private Property

    private PathGeometry FlattenedGeometry
    {
      get
      {
        if( ( m_flattenedGeometry == null ) || ( !m_flattenedGeometryIsValid ) )
        {
          m_flattenedGeometry = m_pathGeometryHelper.FlattenGeometry( this.PathGeometry, 30 );
          m_flattenedGeometryIsValid = true;
        }

        return m_flattenedGeometry;
      }
    }

    #endregion

    #region PointsOnFlattenedPathGeometry Private Property

    private List<Point> PointsOnFlattenedPathGeometry
    {
      get
      {
        if( m_pointsOnFlattenedPathGeometry == null )
        {
          m_pointsOnFlattenedPathGeometry = new List<Point>();
        }

        if( !m_pointsOnFlattendPathGeometryIsValid )
        {
          m_pointsOnFlattenedPathGeometry = GeometryHelper.GetPointsOnFlattenedPath( this.FlattenedGeometry );
          m_pointsOnFlattendPathGeometryIsValid = true;
        }

        return m_pointsOnFlattenedPathGeometry;
      }
    }

    #endregion


    #region ScalingFunction Internal Property

    internal IScalingFunction ScalingFunction
    {
      get
      {
        return m_scalingFunction;
      }
      set
      {
        if( m_scalingFunction != value )
        {
          m_scalingFunction = value;

          if( !this.IsInitializing )
            this.RaiseScalingFunctionChanged();
        }
      }
    }

    internal event EventHandler ScalingFunctionChanged;

    internal void RaiseScalingFunctionChanged()
    {
      if( this.ScalingFunctionChanged != null )
        this.ScalingFunctionChanged( this, EventArgs.Empty );
    }

    #endregion

    #region ZIndexFunction Internal Property

    internal IZIndexFunction ZIndexFunction
    {
      get
      {
        return m_zIndexFunction;
      }
      set
      {
        if( m_zIndexFunction != value )
        {
          m_zIndexFunction = value;

          if( !this.IsInitializing )
            this.RaiseZIndexFunctionChanged();
        }
      }
    }

    internal event EventHandler ZIndexFunctionChanged;

    internal void RaiseZIndexFunctionChanged()
    {
      if( this.ZIndexFunctionChanged != null )
        this.ZIndexFunctionChanged( this, EventArgs.Empty );
    }

    #endregion

    #region PositioningFunction Internal Property

    internal PositioningFunctionBase PositioningFunction
    {
      get
      {
        return m_positioningFunction;
      }
      set
      {
        if( m_positioningFunction != value )
        {
          m_positioningFunction = value;

          if( !this.IsInitializing )
            this.RaisePositioningFunctionChanged();
        }
      }
    }

    internal event EventHandler PositioningFunctionChanged;

    internal void RaisePositioningFunctionChanged()
    {
      if( this.PositioningFunctionChanged != null )
        this.PositioningFunctionChanged( this, EventArgs.Empty );
    }

    #endregion


    #region Private Methods

    private void UpdateFlattenPathGeometry()
    {
      if( this.IsInitializing )
        return;

      m_flattenedGeometryIsValid = false;
      m_pointsOnFlattendPathGeometryIsValid = false;

      this.UpdatePositioningFunction();
    }

    private void UpdatePositioningFunction()
    {
      this.PositioningFunction = new PathPositioning( this.PointsOnFlattenedPathGeometry,
        this.PathGeometry.Bounds,
        this.PathPadding,
        this.PathStretch,
        this.ContainerOrientation,
        this.ContainerPadding,
        this.StartOffset );
    }

    private void UpdateScalingFunction()
    {
      ObservableCollection<ScalingStop> scalingStops = this.ScalingStops;

      if( ( scalingStops == null ) || ( scalingStops.Count == 0 ) )
      {
        this.ScalingFunction = null;
      }
      else
      {
        this.ScalingFunction = new ScalingStopFunction()
        {
          ScalingStops = scalingStops
        };
      }
    }

    private void UpdateZIndexFunction()
    {
      ObservableCollection<ZIndexStop> zIndexStops = this.ZIndexStops;

      if( ( zIndexStops == null ) || ( zIndexStops.Count == 0 ) )
      {
        this.ZIndexFunction = null;
      }
      else
      {
        this.ZIndexFunction = new ZIndexFunction()
        {
          ZIndexStops = zIndexStops
        };
      }
    }

    #endregion


    #region ISupportInitialize Members

    void ISupportInitialize.BeginInit()
    {
      m_initializingCount++;
      this.BeginInitCore();
    }

    void ISupportInitialize.EndInit()
    {
      if( m_initializingCount == 0 )
        throw new InvalidOperationException( "An attempt has been made to call EndInit when no call to BeginInit has been made." );

      m_initializingCount--;

      if( m_initializingCount == 0 )
        this.EndInitCore();
    }

    internal bool IsInitializing
    {
      get
      {
        return ( m_initializingCount > 0 );
      }
    }

    internal virtual void BeginInitCore()
    {
    }

    internal virtual void EndInitCore()
    {
      if( this.PathGeometry == null )
        throw new ListBoxException( "PathGeometry cannot be null." );

      this.UpdateFlattenPathGeometry();
      this.UpdateScalingFunction();
      this.UpdateZIndexFunction();
    }

    private int m_initializingCount;

    #endregion

    #region Private fields

    private PathGeometryHelper m_pathGeometryHelper = new PathGeometryHelper();
    private bool m_pointsOnFlattendPathGeometryIsValid = false;
    private List<Point> m_pointsOnFlattenedPathGeometry;
    private bool m_flattenedGeometryIsValid = false;
    private PathGeometry m_flattenedGeometry;

    private PositioningFunctionBase m_positioningFunction;
    private IScalingFunction m_scalingFunction;
    private IZIndexFunction m_zIndexFunction;

    #endregion
  }
}
